/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <iostream>
#include <sstream>

#include "Utils/DebugStream.hpp"
#include "Utils/Fonts/FontManager.hpp"

using namespace std;

class AutoFace
{
    FT_Face& f;
    bool     own;
public:
    AutoFace(FT_Face& face)
        : f(face), own(true)
    {}

    ~AutoFace()
    {
        if (own) 
        {
            FT_Done_Face(f);
        }
    }
    
    FT_Face release() 
    {
        own = false;
        return f;
    }

    FT_Face get()
    {
        return f;
    }
};

// inline void printCharMap(const FontManager::FM_Face& face)
// {
//     FT_CharMap found = 0; 
//     FT_CharMap charmap; 
//     int        n; 
    
//     for ( n = 0; n < face.mFace->num_charmaps; n++ ) 
//     { 
//         charmap = face.mFace->charmaps[n]; 

//         cerr << "Charmap " << n
//              << ", index:" << FT_Get_Charmap_Index(charmap)
//              << ", platform_id:" << charmap->platform_id
//              << ", encoding_id:" << charmap->encoding_id
//              << ", encoding:("   
//              << (char)((charmap->encoding >>24) & 0xff)
//              << ", " << (char)((charmap->encoding >>16) & 0xff)
//              << ", " << (char)((charmap->encoding >> 8) & 0xff)
//              << ", " << (char)(charmap->encoding  & 0xff)
//              << ")"
//              << endl;

// //         if ( charmap->platform_id == my_platform_id &&  
// //              charmap->encoding_id == my_encoding_id ) 
// //         { 
// //             found = charmap; 
// //             break; 
// //         }
        
//     }
// }


FontManager::FontManager()
    : mCurrentFace(0, 0), 
      mDeleteCurrent(false), 
      mUseKerning(true),
      mHyphenationManager(0)
{
    /* initialize library */
    FT_Error error = FT_Init_FreeType( &mLibrary );
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    mRotateMatrix.xx = 0;
    mRotateMatrix.yx = 65535;
    mRotateMatrix.xy = -65535;
    mRotateMatrix.yy = 0;
}

FontManager::~FontManager()
{
    for(FONTS::iterator p = mFonts.begin();
        p != mFonts.end();
        ++p)
    {
        for (FONT_STYLES::iterator i = p->second.begin();
             i != p->second.end();
             ++i)
        {
            FT_Done_Face(i->second.mFace);
        }
    }

    FT_Done_FreeType( mLibrary );
}


void FontManager::addFont(const std::string& file)
{
    FT_Face face;

    FT_Error error = FT_New_Face( mLibrary, 
                                  file.c_str(),
                                  0, 
                                  &face );
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    AutoFace aface(face);

    addCreatedFace(aface.get());
    
    aface.release();
}

void FontManager::addCreatedFace(FT_Face face)
{
    FM_Face item_face(new GlyphTable, face);

    FONT_STYLES::value_type fname(face->style_name, item_face);
    
    FONTS::iterator p = mFonts.find(face->family_name);
    if (p != mFonts.end())
    {
        p->second.insert(fname);
    }
    else
    {
        FONTS::value_type fnt(face->family_name, FONT_STYLES());
        
        fnt.second.insert(fname);
        mFonts.insert(fnt);
    }
}

void FontManager::useFont(const std::string& file, bool add)
{
    FT_Face face;

    FT_Error error = FT_New_Face( mLibrary, 
                                  file.c_str(),
                                  0, 
                                  &face );
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    AutoFace aface(face);

    FONTS::iterator p = mFonts.find(aface.get()->family_name);
    if (p != mFonts.end())
    {
        FONT_STYLES::iterator i = p->second.find(aface.get()->style_name);
        if (i != p->second.end())
        {
            if (mDeleteCurrent)
            {
                FT_Done_Face(mCurrentFace.mFace);
            }

            mDeleteCurrent = false;
            mCurrentFace = i->second;
            

            //printCharMap(mCurrentFace);

            return;
        }
    }

    if (add)
    {
        addCreatedFace(aface.get());
    }
    
    if (mDeleteCurrent)
    {
        FT_Done_Face(mCurrentFace.mFace);
    }

    mDeleteCurrent = !add;

    mCurrentFace.mFace   = aface.get();
    
    aface.release();
}

bool FontManager::isFontAvailable(const std::string& file, bool download)
{
    FT_Face face;

    FT_Error error = FT_New_Face( mLibrary, 
                                  file.c_str(),
                                  0, 
                                  &face );
    if (error)
    {
        std::string msg (__FUNCTION__);
        msg += ": Cannot open face for ";
        msg += file;

        throw FontManagerError(msg);
    }

    AutoFace aface(face);

    FONTS::iterator p = mFonts.find(face->family_name);
    if (p != mFonts.end())
    {
        return true;
    }
        
    if (!download)
    {
        return false;
    }
    addCreatedFace(aface.get());

    mCurrentFace.mFace   = aface.release();

    return true;
}

void FontManager::useFont(const std::string& font_name, 
                          const std::string& style)
{
    FONTS::iterator p = mFonts.find(font_name);
    if (p == mFonts.end())
    {
        ostringstream os;
        os << __FUNCTION__ 
           << ":Font " << font_name << " is not available"
           << "\nAvailable fonts are:";
        for(p = mFonts.begin(); p != mFonts.end(); ++p)
        {
            os << p->first << endl;
        }
        throw FontManagerError(os.str());
    }

    FONT_STYLES::iterator i;
    if (style.empty())
    {
        i = p->second.begin();
    }
    else
    {
        i = p->second.find(style);
    }

    if (i == p->second.end())
    {
        ostringstream os;
        os << __FUNCTION__ << ": Font style "
           << style << " not found, font "
           << font_name 
           << "\nAvailable styles are:";
        for(i = p->second.begin(); i != p->second.end(); ++i)
        {
            os << i->first << endl;
        }

        throw FontManagerError(os.str());
    }

    if (mDeleteCurrent)
    {
        FT_Done_Face(mCurrentFace.mFace);
    }

    mDeleteCurrent = false;
    mCurrentFace = i->second;

    //printCharMap(mCurrentFace);
}

// Change font too
void FontManager::setStyle(const TextStyle&      style)
{
    isFontAvailable(style.FontFile(), true);

    useFont(style.Font(), style.Style());

    useKerning(style.UseKerning());

    mFontSize = style.Size();
    mAlign    = style.TextAlign();

    FT_Error error = FT_Set_Pixel_Sizes( mCurrentFace.mFace,
                                         0, 
                                         mFontSize);
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

}

// Current font is used
void FontManager::setStyle(const size_t          font_size,
                           const Align           align)
{
    mFontSize = font_size;
    mAlign    = align;

    FT_Error error = FT_Set_Pixel_Sizes( mCurrentFace.mFace,
                                         0, 
                                         mFontSize);
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }
}


int FontManager::renderLine(DrawingArea& area,
                            const char*  str)
{
    size_t num_chars = strlen( str );

    FT_Bool kerning = mUseKerning ? FT_HAS_KERNING(mCurrentFace.mFace): false; 

    GLYPHY_LINE glyph_line;
    // ignore line breaking
    FT_BBox bbox = fillGlyphs(kerning, str, num_chars, glyph_line);

//     /* compute string dimensions in integer pixels */  
//     int string_width = (bbox.xMax - bbox.xMin);
//     int string_height = (bbox.yMax - bbox.yMin);

    int prefix = getAlignLen(area, bbox);
    
    for (GLYPHY_LINE::iterator p = glyph_line.begin();
         p != glyph_line.begin();
        ++p)
    {
        area.pen.X = p->first.X + prefix;

        drawImage(area, 
                  p->second, 
                  0);
    }

    return num_chars;
}

int FontManager::getAlignLen(const DrawingArea& area,
                             const FT_BBox&     bbox)
{
    int prefix;
    int string_width = (bbox.xMax - bbox.xMin);
//    int string_height = (bbox.yMax - bbox.yMin);
    
    switch(mAlign)
    {
        case Left:
            prefix = area.coord.start.X;
            break;
        case Right:
            prefix = area.coord.start.X + area.coord.end.X - string_width;
            break;
        case Center:
            prefix = (area.coord.start.X + area.coord.end.X - string_width)/2;
            break;
        default:
            prefix = 0;
    }

    return prefix;
}

int FontManager::getLineWidth(const char* str)
{
    size_t num_chars = strlen( str );

    FT_Bool kerning = mUseKerning ? FT_HAS_KERNING(mCurrentFace.mFace): false; 

    GLYPHY_LINE glyph_line;
    glyph_line.reserve(num_chars);

    FT_BBox bbox = fillGlyphs(kerning, str, num_chars, glyph_line);

    /* compute string dimensions in integer pixels */  
    int string_width = (bbox.xMax - bbox.xMin);

    return string_width;
}

FT_BBox FontManager::fillGlyphs(FT_Bool      kerning,
                                const char*  str, 
                                size_t&      num_chars, 
                                GLYPHY_LINE& glyph_line,
                                const size_t max_width)
{
    FT_BBox bbox;

    bbox.xMin = bbox.yMin = 32000; 
    bbox.xMax = bbox.yMax = -32000; 

    FT_UInt  previous = 0;
    FT_UInt  glyph_index;

    int    remain = num_chars;
    size_t start  = 0;
    int    error  = 0;

    FT_GlyphSlot slot = mCurrentFace.mFace->glyph;

    GLYPHY z;
    for (int n = 0; n < remain; ++n)
    {
        glyph_index = FT_Get_Char_Index(mCurrentFace.mFace, 
                                        str[n] );

        if ( kerning && previous && glyph_index ) 
        {
            FT_Vector delta; 
            error = FT_Get_Kerning( mCurrentFace.mFace, 
                                    previous, 
                                    glyph_index, 
                                    FT_KERNING_DEFAULT, 
                                    &delta ); 
            if (!error)
            {
                start += delta.x >> 6; 
            }
        }

        if (start > max_width)
        {
            break;
        }

        -- num_chars;

        error = FT_Load_Glyph( mCurrentFace.mFace, 
                               glyph_index, 
                               FT_LOAD_DEFAULT);
        if (error)
        {
            throw FontManagerError(error, __FUNCTION__);
        }

        FT_Glyph glyph;
        if (!mCurrentFace.mGlyphs->find(glyph_index, glyph))
        {
            error = FT_Get_Glyph( mCurrentFace.mFace->glyph, 
                                  &glyph ); 
            if ( error )
            {
                throw FontManagerError(error, __FUNCTION__);
            }

            error = FT_Glyph_To_Bitmap( &glyph, 
                                        FT_RENDER_MODE_MONO,
                                        0, 
                                        1 ); 
            if ( error )
            {
                throw FontManagerError(error, __FUNCTION__);
            }

            mCurrentFace.mGlyphs->insert(glyph_index, glyph);
        }

        FT_BBox glyph_bbox; 

        FT_Glyph_Get_CBox( glyph, 
                           FT_GLYPH_BBOX_PIXELS,
                           &glyph_bbox ); 

        glyph_bbox.xMin += start;
        glyph_bbox.xMax += start;

        if ( glyph_bbox.xMin < bbox.xMin ) 
            bbox.xMin = glyph_bbox.xMin; 

        if ( glyph_bbox.yMin < bbox.yMin ) 
            bbox.yMin = glyph_bbox.yMin; 

        if ( glyph_bbox.xMax > bbox.xMax ) 
            bbox.xMax = glyph_bbox.xMax; 

        if ( glyph_bbox.yMax > bbox.yMax ) 
            bbox.yMax = glyph_bbox.yMax;
        
        if ( bbox.xMin > bbox.xMax ) 
        { 
            bbox.xMin = 0;
            bbox.yMin = 0; 
            bbox.xMax = 0; 
            bbox.yMax = 0; 
        } 

        z.first.X = start;
        z.first.Y = 0;
        z.second = glyph;

        glyph_line.push_back(z);

        start += slot->advance.x >> 6;

        previous = glyph_index;
    }
    
    if (bbox.xMax > max_width)// :FIXME: should be removed when bbox will be ajusted 
        // to show real size
        bbox.xMax = max_width;
    return bbox;
}

int FontManager::renderMultiLines(DrawingArea&     area,
                                  const string&    str,
                                  const TextStyle& style,
                                  bool             imitate)
{
    size_t max_width = area.coord.end.X - area.coord.start.X;
    int    num_chars = str.size();

    FT_Bool kerning = mUseKerning ? FT_HAS_KERNING(mCurrentFace.mFace): false; 

    GLYPHY_LINE glyph_line;
    glyph_line.reserve(num_chars);

    int    lines    = 0;
    size_t remain   = num_chars;
    size_t pos      = 0;
    bool   new_line = false;

    mAlign = style.TextAlign();

    do
    {
        remain = num_chars - pos;
        
        if (new_line)
        {
            area.movePen(0, style.InterLineSpace());
            area.movePen(0, style.Size());// + style.Before();
        }
        else
            new_line = true;

        glyph_line.clear();

        size_t old_pos = pos;

        FT_BBox bbox = fillGlyphs(kerning, 
                                  str.c_str() + pos, 
                                  remain, 
                                  glyph_line,
                                  max_width);

        if (!glyph_line.empty())
            pos += glyph_line.size() - 1;

        //:FIXME: bbox should be recalculated to show real size
        if ((remain != 0)&&
            glyph_line.end() != glyph_line.begin())
        {
            if (style.Hyphenation() && mHyphenationManager)
            {
                // do hyphenation here
                // fisrtly, try to find the word
                int hyph
                    = mHyphenationManager->hyphenateString(str.substr(old_pos),
                                                           glyph_line.size());
                if (hyph != 0) 
                {
                    pos -= glyph_line.size() - hyph - 1;

                    Point last_point = glyph_line[hyph].first;

                    glyph_line.resize(hyph);
                    if (isalpha(str[pos]))
                    {
                        //put hyp sign here
                        glyph_line.push_back(make_pair(last_point, 
                                                       getGlyph('-'))
                            );
                    }
                }
            }
            else
            {
                // just break text on words
                GLYPHY_LINE::iterator to_clear = glyph_line.end();

                for(size_t i = pos; i > 0; --i)
                {
                    if (isblank(str[i]) || ispunct(str[i]))
                    {
                        pos = i;
                        break;
                    }

                    if (--to_clear == glyph_line.begin())
                        break;
                }
                
                if (to_clear != glyph_line.begin())
                {
                    glyph_line.erase(to_clear, glyph_line.end());
                }
            }

        }
        ++lines;

        if (!imitate)
        {
            int prefix = getAlignLen(area, bbox);

            for (GLYPHY_LINE::iterator p = glyph_line.begin();
                 p != glyph_line.end();
                 ++p)
            {
                area.pen.X = p->first.X + prefix;

                drawImage(area, 
                          p->second, 
                          0);
            }
        }
        
    }while(remain > 0);

    return lines;
}

void FontManager::renderChar(DrawingArea& area,
                             const char&  symbol)
{

    FT_Glyph slot = getGlyph(symbol);
    
    drawImage(area, 
              slot, 
              0);
}

bool FontManager::drawImage(DrawingArea& area, 
                            FT_Glyph     g, 
                            const int    start)
{
    if (g->format != FT_GLYPH_FORMAT_BITMAP)
    {
        cerr << "CAST impossible, bad GLYPH" << endl;
    }
    FT_BitmapGlyph glyph = (FT_BitmapGlyph)g;

    int rowSize = glyph->bitmap.pitch;

    area.pen.X += start;
    
    for(int y = 0;
        y < glyph->bitmap.rows;
        ++y)
    {
     	int y1 = area.pen.Y + y - glyph->top;
        if (y1 <= 0) y1 = 0;

        if (y1 + 1 > (long)area.coord.end.Y)
            return false;

       	for( int x = 0;
             x < glyph->bitmap.width;
             ++x)
        {
            int x1 = x + area.pen.X + glyph->left;

            if (x1 + 1 > (long)area.coord.end.X)
                return false;

            unsigned char* src = glyph->bitmap.buffer 
                + y * rowSize 
                + (x >> 3);

            if((*src >> (7 - x&0x7)) & 0x1)
            {
                unsigned char* des 
                    = (unsigned char*) area.buffer 
                      + y1 * area.width 
                      + (x1 >> 2);

                *des &= ~(0xC0 >> ((x1 & 0x3) << 0x1));
            }
        }
    }

    return true;
}

FT_Glyph FontManager::getGlyph(const char symbol)
{
    FT_Glyph slot ;

    FT_UInt glyph_index = FT_Get_Char_Index( mCurrentFace.mFace, symbol);

    if (!mCurrentFace.mGlyphs->find(glyph_index, slot))
    {
        int error = FT_Load_Glyph( mCurrentFace.mFace,
                                   glyph_index, 
                                   FT_LOAD_DEFAULT);
        if (error)
        {
            throw FontManagerError(error, __FUNCTION__);
        }

        error = FT_Get_Glyph( mCurrentFace.mFace->glyph, 
                              &slot ); 
        if ( error )
        {
            throw FontManagerError(error, __FUNCTION__);
        }

        error = FT_Glyph_To_Bitmap( &slot, 
                                    FT_RENDER_MODE_MONO,
                                    0, 
                                    1 ); 
        if ( error )
        {
            throw FontManagerError(error, __FUNCTION__);
        }

        mCurrentFace.mGlyphs->insert(glyph_index, slot);
    } 

    return slot;
}
